Pe File Format Tutorial
Както се подразбира и от заглавието в този Tut ще разгледаме Pe File Format. Имах идея да напиша Tut само за Pe Header,но после реших че ще е по-добре ако се запознаем изцяло с Pe File Format.
Първо трябва да кажа,за тези който си нямат представа за Pe File Format какво като цяло представлява той.Това е формата на exe-тата който са създадени за да работят под опереционна система Windows.За Linux и за другите Unix системи формата се нарича Coff.Предполагам че за хората,които за първи път чуват за тези неща,това което казвам нищо не им говори затова препоръчвам на тези който тепърва започват да го изучават да прочетат първо малко по-обща литература.
Стига съм давал съвети!Нека да започваме с Tut-a!;)
Да видим първо каква е структурата на едно exe.За целта ще разгледаме едно exe,което трябва да има всеки,който е под Windows.Notepad.exe е най-подходящ за обучение.Затова направете си резервно копие на notepad.exe.
Като за начало ще ви поzкажа една схема,за да моvете да си представите как изглежда едно EXE в Pe File Format.Ако сте чели и други Tutorial-и за Pe Header може би ще се зачудите защо дават различни картинки доста често;)Всъщност всички "картинки",които сте разглеждали са точни и верни.Просто са по-подробни или съответно по-малко подробни!
|
Dos-stub |
|
Pe Signature |
|
File Header |
|
Optional Header |
|
Section Table |
|
Section 1(.text) |
|
Section 2(.data) |
|
Section 3(.edata) |
|
Section 4(.idata) |
|
Section5(.reloc) |
|
Section n..... |
|
COFF Line Numbers |
|
COFF Symbols |
|
Code View Debug Information |
Но мисля че тази схема е достатъчно подробна!;)или поне се надявам да е така!Сега...тези неща който ви ги показвам може още да не ги разбирате но повярвайте ми и аз не сам ги разбрал от веднъж!Затова за да разберете по-добре структурата нека да започнем с нашето примерно EXE(notepad.exe).
Когато отворите notepad.exe с Hexeditor ще видите:

Ако сте на Win 98 или 95 няма да ви съвпада стуктурата на Pe Header-а на notepad.exe с моята за winXP.Но се надявам поне да можете да направите бързо аналогията.
Сега виждате частта от Pe File Format,която се нарича Dos-stub,която се състои от:
->DOS-Stub(на
notepad.exe)
e_magic: 0x5A4D ;Dos Signature
e_cblp: 0x0090
e_cp: 0x0003
e_crlc: 0x0000
e_cparhdr: 0x0004
e_minalloc: 0x0000 ;минимално
място в рама
да се
предвиди за
нашата
програма
e_maxalloc: 0xFFFF;максималното
място,което
да се
предвиди за
програмата
e_ss: 0x0000
e_sp: 0x00B8
e_csum: 0x0000
e_ip: 0x0000
e_cs: 0x0000
e_lfarlc: 0x0040
e_ovno: 0x0000
e_res:
0x0000000000000000
e_oemid: 0x0000
e_oeminfo: 0x0000
e_res2:
0x0000000000000000000000000000000000000000
e_lfanew: 0x000000E8 ;съдержа Offset-a,където
започва Pe Header
Нека да погледнем в нашя Hex Editor на Offset 0x00000000.Виждате 4D5A,което в Assci Code е MZ,което (MZ) показва че имаме валиден Dos-Stub.Сега погледнете и първия член на Dos-Stub(по-горе със син шрифт)Забелязвате,че редът на записването е обратен.Това е така защото в паметта байтовете се зареждат в обратен ред.Тоест ако виждате в вашия Hex Editor 12 34 в паметта тези байтове ще са представени 34 12.Няма да разглеждаме всички членове на Dos-Stub поради простата причина,че нашата програма е направена да върви под Windows и следователно Pe Header-a e много по-важен.От Dos-Stub можем единствено да разберем къде започва Pe Header.На оffset 3C се намира VA(virtual adress) където започва Pe Header-a.След Dos-Stub има празно пространсто от байтове където обикновенно се намира String подобен на :"This program cannot be runned in dos mode"(както е в случая)След като стигнем до offset 0x3C виждаме члена на Dos-Stub наречен e_lfanew .На този offset се намира Е800,което е VA на началото на Pe Header-a.Следователно адреса,на който ще се намира е 0x00E8.Както казахме там започва Pe Header-a!
Отидете на този Offset и ще видите 5045(в Hex Editor).Пак повтарям,че тази стойност в RAM-a ще изглежда 4550.Аssci Code-a na 5045 e PE.По този начин Loader-a na Windows разпознава че имаме валиден Pe Header.Забравих да спомена,че рози String е терминиран(завършва) с 0000.
Веднага след това е началото на File Header.Той е част от Pe Header-a.Ето и неговата структура!
File Header
Machine: 0x014C (I386)
NumberOfSections: 0x0003
TimeDateStamp: 0x3B7D840D (GMT: Fri Aug
17 20:52:29 2001)
PointerToSymbolTable: 0x00000000
NumberOfSymbols: 0x00000000
SizeOfOptionalHeader: 0x00E0
Characteristics: 0x010F
(RELOCS_STRIPPED)
(EXECUTABLE_IMAGE)
(LINE_NUMS_STRIPPED)
(LOCAL_SYMS_STRIPPED)
(32BIT_MACHINE)
Предполагам че подразбирате каква е функцията на първия член на File Header-a.Тaзи стойност е дълга WORD показва на каква машина трябва да работи EXE-то.В случая на компютър Intel 386 или по-нов.Това са стойностите който могат да бъдат зададени за типа на машината,на която ще работи EXE-то.
|
0x14d |
Intel
i860 |
|
0x14c |
Intel
I386 (същото е
използвано
за 486 и 586) |
|
0x162 |
MIPS
R3000 |
|
0x166 |
MIPS
R4000 |
|
0x183 |
DEC
Alpha AXP |
Втория Word от File Header-a показва колко секции има exe-то(или DLL-a).В случая те са 3.(Още веднъж обращам внимание,че ако гледате стойностите в Hex Editor-a ви те ще са в обратен ред).
TimeDateStamp!Той е дълъг DWORD(тоест 4 байта) и показва кога exe-то е било направено(Fri Aug 17 20:52:29 2001).
PointerToSymbolTable-тези байтове при всичи exe-та(DLL) са 00000000.На този offset се съдържа адреса,където започва таблицата на COFF-символите.Това поле и следващото се използват като информация при debug-ване.
NumberOfSymbols-съответно съдържа броя на Coff символите.
Сега следва SyzeOfOptionalHeader.Това е следващия голям член на Pe Header.Той следва веднага след File Header.И на това място Loader-a може да разбере колко е голям Optional Header-a в байтове.
След SyzeOfOptionalHeader следва Charakteristics.Tук се дава информацията дали File-ът е EXE,DLL или OBJ.
Стоиността 0x010F показва,че file-ът има следните характеристики:(RELOCS_STRIPPED),(EXECUTABLE_IMAGE),(LINE_NUMS_STRIPPED),(LOCAL_SYMS_STRIPPED),(32BIT_MACHINE)
Tова показва,че File-ът е exe за 32 битова машина,няма relocation(пренасочване) и няма Coff символи.
Със следващия байт започва Optional Header.Докато File Header-a дава най-обща информация за нашия File(notepad.exe)-на каква машина той трябва да върви,дали е Exe,Dll или Obj,кога е създаден и т.н..Optional Header дава по-подробна информация,за да може File-ът да бъде успешно зареден и изпълнен в пометта.
Това е и структyрата на Optional Header-a на notepad.exe.
Optional Header
Magic: 0x010B (HDR32_MAGIC)
MajorLinkerVersion: 0x07
MinorLinkerVersion: 0x00 -> 7.00
SizeOfCode: 0x00006E00
SizeOfInitializedData: 0x0000A600
SizeOfUninitializedData: 0x00000000
AddressOfEntryPoint: 0x00006AE0
BaseOfCode: 0x00001000
BaseOfData: 0x00008000
ImageBase: 0x01000000
SectionAlignment: 0x00001000
FileAlignment: 0x00000200
MajorOperatingSystemVersion: 0x0005
MinorOperatingSystemVersion: 0x0001
-> 5.01
MajorImageVersion: 0x0005
MinorImageVersion: 0x0001 -> 5.01
MajorSubsystemVersion: 0x0004
MinorSubsystemVersion: 0x0000 -> 4.00
Win32VersionValue: 0x00000000
SizeOfImage: 0x00013000
SizeOfHeaders: 0x00000400
CheckSum: 0x0001D855
Subsystem: 0x0002 (WINDOWS_GUI)
DllCharacteristics: 0x8000 (TERMINAL_SERVER_AWARE)
SizeOfStackReserve: 0x00040000
SizeOfStackCommit: 0x00011000
SizeOfHeapReserve: 0x00100000
SizeOfHeapCommit: 0x00001000
LoaderFlags: 0x00000000
NumberOfRvaAndSizes: 0x00000010
Както забелязвате той е доста по-голям от File Header-a,съдържа много повече информация за File-а и има съответно много по-сложна структура.
Първия член на Optional Header-a e дълъг Word и винаги е 010B .Тази стойност е Signatur-a на Optional Header-a(по нея Windows Loader разпорнава,че това е началото на Optional Header).
MajorLinkerVersion: 0x07 (Byte)
MinorLinkerVersion: 0x00 (Byte)
От тези две стойности може да се разбере с коя версия на даден Linker или съответно Compiler е създадено EXE-тo или Obj File.
SizeOfCode: 0x00006E00 (Dword)
Показва размера на целия код.
SizeOfInitializedData: 0x0000A600 (Dword)
Размера на инициялизираната информация(информация,която е налице още при стартирането на програмата)Или по друг начин обяснено:променливи, който заемат място когато File-ът е на hard disk-а.
SizeOfUninitializedData: 0x00000000 (Dword)
Размерът на секциите в EXE-то,който съдържат не иннициялизирана информация(,място за информацията,което е нужно да бъде предвидено при изпълнението на програмата)Но това са променливи(информация),която не заема място на твърдия диск(hard disk-a).
AddressOfEntryPoint: 0x00006AE0 (Dword)
Тука се съдържа offset-a,на който е Entry Point-a(RVA-то от където започва изпълнението на програмата)
BaseOfCode: 0x00001000 (Dword)
Показва къде започва Code секцията.Обикновенно Code секцията започва преди Data секцията.И при Microsoft Linker-a Code секцията започва обикновенно от 0x1000.Code секцията се зарежда след Pe Header-a и Section Table и преди Data секцията.
BaseOfData: 0x00008000 (Dword)
Сочи към RVA(Relativ Virtual Address),където започва Data секцията.Обикновенно Data секцията се зарежда последна в паметта.
ImageBase: 0x01000000 (Dword)
Показва адреса от където започва да се зарежда програмата в паметта.В Exe-та направени да вървят под WinNT адресът в това поле по подразбиране е 10000.A сега за всички Win32 EXE-та Microsoft напражи по подразбиране този адрес да е 400000.
SectionAlignment: 0x00001000 (Dword)
Tози член на Optional Header-a служи за да се определи колко Virtual Address Space(виртуално адресно пространство) трябва да се отдели за дадена секция.Понеже това най-вероятно нищо не ви говори ще ви дам един пример.Тука стойността е 1000h(4096d) Следователно ако първата секция на exe-то започва започва от Virtual Address 0x00001000 и нейния размер е 0x00006D72(тази информация е взета от LordPe на notepad.exe).Следващата секция няма да бъде заредена от адрес 0x00007D72 a от 0x00008000,защото SectionAlignment=0x1000.Стойността ще бъде закръглена до следващото цяло число(8000).
FileAlignment: 0x00000200 (Dword)
Taзи стойност се използва когато трябва да се определи къде във File-ът започва секцията .Големината на всяка секция в File-ът(не в паметта) трябва да бъде закръгляна с стойността на FileAlignment .Тука стойността е 0x200 =>aко първата секция започва на offset 200h и размерът и е 30 байта следващата секция няма да започва на offset 230,а на offset 0x400.В нашето notepad.exe първата секция има размер действителен размер VirtualSize: 0x00006D72 и започва от Offset PointerToRawData: 0x00000400.Следователно логично е следващата секция да започва от : VirtualSize: 0x00006D72+ PointerToRawData: 0x00000400 = 7172.Но Microsoft закръгля тази стойност на VirtualSize: 0x00006D72 на 6Е00.Следващата секция ще започва на Offset 0x7200(6E00+400).И пространството от 8E bytes остава нули ot 7172 do 7200(Ако искате да се оверите отидете на Offset 7172 и вижте че там има нули.Най вероятно се чудите защо нещата са така yсложнени.Но всъщтност е много по лесно да се работи с закръглени числа.
MajorOperatingSystemVersion: 0x0005 (Word)
MinorOperatingSystemVersion: 0x0001
-> 5.01 (Word)
Тези полета показват минималната оперираща система на която трябва да върви Exe-to.
MajorImageVersion: 0x0005
(Word)
MinorImageVersion: 0x0001 -> 5.01(Word)
Тука можете да разберете с коя версия на Linker-a е създадено exe-то.
MajorSubsystemVersion:
0x0004
(Word)
MinorSubsystemVersion: 0x0000 ->
4.00 (Word)
Тука се съдържа информация за минималната OS version ,на която EXE-то може да работи.Стойността 0004 показва че минималната версия трябва да е WinNT 4.00.
Win32VersionValue: 0x00000000 (Dword)
Изглежда че тази стойност винаги е 0.
SizeOfImage: 0x00013000 (Dword)
Това е цялото място необходимо на програмата за да бъде заредена в паметта.Започва от стойността на ImageBase и до края и на последната секция.
SizeOfHeaders: 0x00000400 (Dword)
Това е разбера на целия Pe Header + Section Table.Тука не се включват секциите.
CheckSum: 0x0001D855 (Dword)
Това е за CRC проверката(проверява размера на File-a,който трябва да съответства)Това се използва често като защитна мярка срещу кракерите,защото като се кракне дадено EXE.To и му се разшири размера придобива различно CRC(уникална стойност за всеки File).
Subsystem: 0x0002 (WINDOWS_GUI) (Dword)
Вида на системата на която трябва да тругне програмата.В този случай notepad.exe тръгва под система Windows Graphik User Interface(appz;)).В таблицата долу са представени и другите възможни вариянти.
|
NATIVE
|
1 |
не
изисква
подсистема
както и
драйвери |
|
WINDOWS_GUI
|
2 |
тръгва
под система
Windows GUI |
|
WINDOWS_CUI
|
3 |
тръгва
под система
Windows CUI |
|
OS2_CUI
|
5 |
тръгва
когато е
налична OS/2 CUI
система |
|
POSIX_CUI
|
7 |
тръгва
под ситема POSIX CUI |
*Забележка:За улеснение можете да търсите всички стойности,който обсъждаме във вашя Hex Editor-a,само че в обратен ред;)!
DllCharacteristics: 0x8000 (TERMINAL_SERVER_AWARE) (Word)
Това поле е запълнено с флагове който показват под какви обстоятелства да бъде викана DLL функция.По принцип тука винаги е запълнено с 0000(защото това не е Dll,а Exe:)).Ето и други стойности,който могат да бъдат на това място.
|
1 |
Извикай
функцията,когато
DLL-a е зареден |
|
2
|
извикай я когато нишката завърши |
|
4 |
извикай
я когато
нишката
започва |
|
8 |
извикай
я когато DLL-a
съществува |
SizeOfStackReserve: 0x00040000 (Dword)
Още при зареждането на програмата се определя(резервира) определено количество адресно пространство(не RAM).Тази слтойност служи за това колко адресно пространство да се резервира.
SizeOfStackCommit: 0x00011000 (Dword)
Тука се определя в действителност в RAM-a колко пространство ще бъде използвано.
SizeOfHeapReserve: 0x00100000 (Dword)
Количеството виртуална памет,което трябва да бъде запазено за процесa в heap-a.
SizeOfHeapCommit: 0x00001000 (Dword)
Мястото,което ще буде използвано още в началото на програмата за процеса в heap-a.Например,ако е определено за SizeOfHeapCommit 64kb,то heap-a ще започва от 64kb.А ако в SizeOfHeapReserve e определено 1 MB следоватено процесите в heap-a ще се увеличава до 1 MB.Като този 1 MB ще е разделен на 64 kb-тови зони.
LoaderFlags: 0x00000000 (Dword)
Тези флагове са почти винаги 00000000.Ако тези флагове са установени,това означава че програмата позволява да буде debug-вана.
NumberOfRvaAndSizes: 0x00000010 (Dword)
Тука можете да видите броя на попълненията в Data Directory,който винаги са 16.Стойността 10 е в HEX Code.В decimel тя е 16.;)
Както виждата в Optional Header има много полета предвидени от Microsoft,които обаче не намират още приложение,затова не представляват и никакъв интерес.
Ето и тази прословута Data Directory;)Дойде и ред на нея!Сега разгледайте внимателно структурата по долу на Data Directory на notepad.exe
DataDirectory (16) RVA Size
------------- ---------- ----------
ExportTable 0x00000000 0x00000000
ImportTable 0x00006D20 0x000000C8
(".text")
Resource 0x0000A000 0x00008948 (".rsrc")
Exception 0x00000000 0x00000000
Security 0x00000000 0x00000000
Relocation 0x00000000 0x00000000
Debug 0x00001340 0x0000001C
(".text")
Copyright 0x00000000 0x00000000
GlobalPtr 0x00000000 0x00000000
TLSTable 0x00000000 0x00000000
LoadConfig 0x00000000 0x00000000
BoundImport 0x00000258 0x000000D0
IAT 0x00001000 0x00000324
(".text")
DelayImport 0x00000000 0x00000000
COM 0x00000000 0x00000000
Reserved 0x00000000 0x00000000
Това е цялата структура на notepad.exe.От следващият байт(след NumberOfRvaAndSizes) започва нашата Data Directory с нейнете членове в нея.Data Directory съдържа всички RVA-та и размери на важните части от EXE-то.
Първия член е:
ExportTable 0x00000000 0x00000000 (Dword) (Dword)
В него се съдържа информация за Export Table,а именно къде започва Export Table и колко е нейния размер.В този случай нямаме зададени стойности за Export Table нито размер,защото такава Export Table няма в notepad.exe.В повечето exe-та няма Export Table a обикновено в DLL-те.В Export Table се намират всички функции,който са в Еxe-то,а не се взимат от DLL или са си в самия DLL.За сега запомнете само това,че Export Table е типичен за DLL-те,които export-ват(предоставят функции,който да се използват от друг файл) функции.В следващия Tutorial ще отделим по-голямо внимание на Export Table и нейната структура.
ImportTable 0x00006D20 0x000000C8 (".text") (Dword) (Dword)
Import Table-тука се съдържа адреса,където започва Import Table и неговия размер.За notepad.exe Import Table се намира в .text секцията.По-късно ще разгледаме какво представлява Import Table.
Resource 0x0000A000 0x00008948 (".rsrc") (Dword) (Dword)
Това е ресурс директорията и съответно нейното RVA и размер.В ресурс директорията както се подразбира и от името и се съдържат всички ресоурси от който се нуждае exe-то.Например ресурс е някакув .bmp File(Вижте тута на ^Shade^ за структура на bmp файлове)Но аз дадох лош пример.Ресурси,който са в exe-то са иконите,диалозите,формите....Всички те са в .rsrc section.Resource section ще я обясня в продължението на този Tutorial.
Exception 0x00000000 0x00000000 (Dword) (Dword)
Нямам представа за какво служи това попълнение на Data Directory.И не съм срештал досега това поле да има различни цифри освен нули;)
Security 0x00000000 0x00000000 (Dword) (Dword)
Това е директорията за сигурност.Също нямам представа за какво служи.Забелязвам че винаги е 00.......Най-вероятно Microsoft са я предвидили за вбъдещето,когато ще навлезе нейната употреба.
Relocation 0x00000000 0x00000000 (Dword) (Dword)
В това EXE notepad няма relocations,затова тези полета са нули..Иначе в relocation data се съдържа допълнителна информация,която помагана loader при заредено EXE да намира точните адреси на който се намира търсената информация.Например:Когато ImageBase:400000 loader-а започва да зарежда File-а от адрес 400000,но по някакви причини loader не може да зареди File-ът на този адрес.Той зарежда File-ът на адрес 500000(примерно).Сега когато трябва да се потърси дадена информация възниква проблема на loader-a.Той търси информацията на VA-то(Virtual Adress=RVA+ImageBase)Примерно offset 0xE6AB съдържа сочещия адрес към информацията,която е търсена(например String-да речем някакви букви:ABC) и от този offset loader-a разбира,че тази информация 'ABC' се намира на VA 00401EA6.Но понеже Exe-то не е заредено на адрес 400000(Image Base) =>даденото VA(00401EA6) e грешно.В relocation data се съдържа нужната информация,за да се намери дадения String('ABC')За да се намери този String трябва да се извади от адреса на който е зареден File-ът(500000) Image Base(400000).Следователно ще стане 500000-400000=100000 и тази стойност се нарича delta.За да се намери равилното VA на String-a трябва да се направи следното:VA(00401EA6+100000=00501EA6)Taka Loader-a ще намери верното VA,на който се намира String-a('ABC').
Debug 0x00001340 0x0000001C (".text") (Dword) (Dword)
Тука се съдържа информация за RVA-то и размера на Debug секцията или за Debug информация в някаква друга секция(както е тука).Тука няма отделна debug секция,а debug информацията е сложена в .text секцията.Това изцяло зависи от Compiler-a той "решава" дали да сложи Debug информацията в отделна секция.
Copyright 0x00000000 0x00000000 (Dword) (Dword)
Това поле не играе важна роля..То не e задължително,а ако го има дава информация за RVA-то и размера на място,където се съдържа информация за авторските права.Нампример то ще сочи размера и адреса на String,който казва,че тази програма принадлежи на John някакъв си.Обикновенно това поле сочи String терминиран с нули(Null terminated String).
GlobalPtr 0x00000000 0x00000000 (Dword) (Dword)
Нямам си никаква идея за какво служат тези полета.Не са особено важни;)Явно и не служат за нищо защото винаги са 000.....
TLSTable 0x00000000 0x00000000 (Dword) (Dword)
Директория в която се представят локалните нишки.Това поле сочи към мястото,където се намират променливи,който показват нишките в exe-то и показва размера му.Всяка променлива представлява дадената нишка в EXE-то.
LoadConfig 0x00000000 0x00000000 (Dword) (Dword)
И това поле не знам за какво служи.Не сум намерил някъде логични обеснения за него.То не се използва и не играе важна роля.
BoundImport 0x00000258 0x000000D0 (Dword) (Dword)
Показва RVA-то на Bound Import и неговия размер.Bound Import ще разясня какво преставлява когато говоря по подробно за Imported Symbols.
IAT 0x00001000 0x00000324 (".text") (Dword) (Dword)
Сочи към полето където са представени адресите на Imported Functions и показва размрера на това поле.повече за това поле -когато обяснявам за Imported Functions.
Последните три полета не са ми познати а и не се използват ,или поне аз не сам виждал да бъдат използвани.
С това приключи първата част от Pe File Format Tutorial-a.Надявам се повечето неща да са ясни.Ако сте начинаещи най-вероятно е че ]е имате много трудности при разбирането на Pe Fiole Format.Затова препоръчвам да прочетете Tutorial-ите на Iczelion www.win32asm.cjb.net.Има и много други есета на тази тема който биха ви дали много по-подробна информация.LUEVELSMEYER's pe.txt-много хубаво есе прочетете го.
Едно от най-добрите есета на тази тема,който съм срещал досега е:
Peering Inside the PE: A Tour of the Win32 Portable Executable File
Format" (M. Pietrek), in: Microsoft Systems Journal 3/1994
-потърсете за
него в www.google.com
Надявам се че този Tutorial ви е бил полезен и сте научили поне малко повече за Pe File Format.За забележки и кометари можете да ми пишете на stan4oo@abv.bg.
*Забележка:В този Tutorial съм работил с notepad.exe за WinXP.Затова имайте в предвид че стойностите най-вероятно ще бъдат различни.
Image Base-мястото в паметта,от където започва да се зарежда програмата
RVA-Relative Virtual Address.Това е стойността,която трябва да добавите към Image Base за да получите линеарния адрес.На този линеарен адрес се нужната ни информация,която е заредена в паметта.
VA-Virtual Address=Image Base + RVA=линеарен
адрес
Byte=8 bits
Word=2 Bytes=16 Bits
Dword=2 Words=4Bytes=32 bits